using System;
using UnityEngine;
using DanielSig.UnityExtensions;

namespace DanielSig.Geometry
{
	public struct Line3D
	{
		public const float DEFAULT_THICKNESS = 0.001F;
		
		public Line3D(ref Vector3 start, ref Vector3 end)
		{
			_magnitude = Vector3.Distance(start, end);
			_radius = DEFAULT_RADIUS;
			
			_startX = start.x;
			_startY = start.y;
			_startZ = start.z;
			
			float invMagn = 1F / _magnitude;
				
			_normalX = (end.x - _startX) * invMagn;
			_normalY = (end.y - _startY) * invMagn;
			_normalZ = (end.z - _startZ) * invMagn;
		}
		public Line3D(ref Vector3 start, ref Vector3 end, float thickness)
		{
			_magnitude = Vector3.Distance(start, end);
			_radius = thickness * 0.5F;
			
			_startX = start.x;
			_startY = start.y;
			_startZ = start.z;
			
			float invMagn = 1F / _magnitude;
				
			_normalX = (end.x - _startX) * invMagn;
			_normalY = (end.y - _startY) * invMagn;
			_normalZ = (end.z - _startZ) * invMagn;
		}
		public Line3D(Vector3 start, Vector3 end, float thickness)
		{
			_magnitude = Vector3.Distance(start, end);
			_radius = thickness * 0.5F;
			
			_startX = start.x;
			_startY = start.y;
			_startZ = start.z;
			
			float invMagn = 1F / _magnitude;
			
			_normalX = (end.x - _startX) * invMagn;
			_normalY = (end.y - _startY) * invMagn;
			_normalZ = (end.z - _startZ) * invMagn;
		}
		public Line3D(Vector3 start, Vector3 end)
				: this(start, end, DEFAULT_THICKNESS)
		{
		}
		public Vector3 Start
		{
			get
			{
				return new Vector3(_startX, _startY, _startZ);
			}
		}
		public Vector3 End
		{
			get
			{
				return new Vector3(_startX + _normalX * _magnitude,
				                   _startY + _normalY * _magnitude,
				                   _startZ + _normalZ * _magnitude);
			}
		}
		public Vector3 Center
		{
			get
			{
				return GetPointOnLine(0.5F);
			}
		}
		public Vector3 Normal
		{
			get
			{
				return new Vector3(_normalX,
				                   _normalY,
				                   _normalZ);
			}
		}
		public float Length
		{
			get
			{
				return _magnitude;
			}
		}
		public float Thickness
		{
			get
			{
				return _radius * 2F;
			}
		}
		public bool IsPointOnLine(Vector3 point)
		{
			Vector3 v = point - Start;
			float dist = Normal.Dot(v);
			if(dist < 0F)
			{
				return v.sqrMagnitude < _radius * _radius;
			}
			if(dist > _magnitude)
			{
				v = point - End;
				return v.sqrMagnitude < _radius * _radius;
			}
			dist = (Normal * dist).DistanceToSqr(v);
			return dist < _radius * _radius;
			/*
			float x = point.x - _startX;
			float y = point.y - _startY;
			float z = point.z - _startY;
			float offset = x * (x-_normalX) + y * (y-_normalY) + z * (z-_normalZ);
			
			if(offset < -_radius || offset > _radius)
			{
				return false;
			}
			
			float t = x * _normalX + y * _normalY + z * _normalZ;
			if(t < 0F || t > _magnitude)
			{
				return false;
			}
			return true;*/
		}
		public Vector3 GetPointOnLine(float t)
		{
			if(t > 1F)
			{
				return new Vector3(_startX + _normalX * _magnitude,
				                   _startY + _normalY * _magnitude,
				                   _startZ + _normalZ * _magnitude);
			}
			else if(t < 0F)
			{
				return new Vector3(_startX, _startY, _startZ);
			}
			
			t *= _magnitude;
			return new Vector3(_startX + _normalX * t,
			                   _startY + _normalY * t,
			                   _startZ + _normalZ * t);
		}
		public static Line3D operator +(Line3D a, Line3D b)
		{
			float a1b1 = a.Start.DistanceToSqr(b.Start);
			float a1b2 = a.Start.DistanceToSqr(b.End);
			float a2b1 = a.End.DistanceToSqr(b.Start);
			float a2b2 = a.End.DistanceToSqr(b.End);
			float a1a2 = a._magnitude * a._magnitude;
			float b1b2 = b._magnitude * b._magnitude;
			
			float max = Mathf.Max(a1b1, a1b2, a2b1, a2b2, a1a2, b1b2);
			
			if(max == a1b1) return new Line3D(a.Start, b.Start, a._radius + b._radius);
			if(max == a1b2) return new Line3D(a.Start, b.End, a._radius + b._radius);
			if(max == a2b1) return new Line3D(a.End, b.Start, a._radius + b._radius);
			if(max == a2b2) return new Line3D(a.End, b.End, a._radius + b._radius);
			if(max == a1a2) return a;
			return b;
		}
		private const float DEFAULT_RADIUS = 0.0005F;
		private float _magnitude;
		private float _radius;
		
		private float _startX;
		private float _startY;
		private float _startZ;
		
		private float _normalX;
		private float _normalY;
		private float _normalZ;
	}
}

